/**
 * Author: ling
 * Create Time: 2018-11-28 17:11
 * Description: 策略模式
 */
// 最初版代码

/**
 * calculateBonus函数比较庞大，包含了很多if-else语句，这些语句需要覆盖所有的逻辑分支。
 * calculateBonus函数缺乏弹性，
 * 如果增加了一种新的绩效等级C，或者想把绩效S的奖金系数改为5，那我们必须深入calculateBonus函数的内部实现，
 * 这是违反开放-封闭原则的。算法的复用性差，如果在程序的其他地方需要重用这些计算奖金的算法呢？我们的选择只有复制和粘贴。
 * @param level
 * @param salary
 * @returns {number}
 */
calculateBonus = function (level, salary) {
  if(level === 'S') {
    return salary * 4
  }
  if(level === 'A') {
    return salary * 3
  }
  if(level === 'B') {
    return salary * 2
  }
};
// 采用组合函数重构代码
/**
 * 目前，我们的程序得到了一定的改善，但这种改善非常有限，我们依然没有解决最重要的问题：
 * calculateBonus函数有可能越来越庞大，而且在系统变化的时候缺乏弹性。
 */
performanceS = function (salary) {
  return 4 * salary
};
performanceA = function (salary) {
  return 3 * salary
};
performanceB = function (salary) {
  return 2 * salary
};
calculateBonus = function (level, salary) {
  if(level === 'S') {
    return performanceS(salary)
  }
  if(level === 'A') {
    return performanceA(salary)
  }
  if(level === 'B') {
    return performanceB(salary)
  }
};
// 传统面向对象语言的策略模式
performanceS = function() {};
performanceS.prototype.calculate = function (salary) {
  return 4 * salary
};
performanceA = function() {};
performanceA.prototype.calculate = function (salary) {
  return 3 * salary
};
performanceB = function() {};
performanceB.prototype.calculate = function (salary) {
  return 2 * salary
};
Bonus = function () {
  this.salary = null;
  this.strategy = null;
};
Bonus.prototype.setSalary = function (salary) {
  this.salary = salary
};
Bonus.prototype.setStrategy = function (strategy) {
  this.strategy = strategy
};
Bonus.prototype.getBonus = function () {
  return this.strategy.calculate(this.salary)
};
// js版本的策略模式
strategies = {
  'S': function (salary) {
    return 4 * salary
  },
  'A': function (salary) {
    return 3 * salary
  },
  'B': function (salary) {
    return 2 * salary
  }
};
calculateBonus = function (level, salary) {
  return strategies[level](salary)
};
// 策略模式重构表单验证
strategies = {
  isNonEmpty: function (value, errMsg) {
    if(value === '') {
      return errMsg
    }
  },
  minLength: function (value, length, errMsg) {
    if(value.length < length) {
      return errMsg
    }
  },
  isMobile: function (value, errMsg) {
    if(!/^1[0-9]{10}$/.test(value)) {
      return errMsg
    }
  }
};

Validator = function () {
  this.cache = [];
};
Validator.prototype.add = function (value, rules) {
  let __this = this;

  for (let rule of rules) {
    (function(value, rule) {
      // 把strategy与参数分开
      let strategyArr = rule.strategy.split(':');
      let errorMsg = rule.errorMsg;

      __this.cache.push(function () {
        let strategy = strategyArr.shift();
        strategyArr.unshift(value);
        strategyArr.push(errorMsg);

        return strategies[strategy].apply(null, strategyArr)
      })
    }(value, rule))
  }
};
Validator.prototype.start = function () {
  for (let validatorFunc of this.cache ) {
    let errorMsg = validatorFunc();
    if(errorMsg) {
      return errorMsg
    }
  }
};

validatorFunc = function () {
  let validator = new Validator();

  /***************添加一些校验规则****************/
  validator. add( registerForm.userName, [
    {strategy: 'isNonEmpty', errorMsg: '用户名不能为空'}
  ]);
  validator. add( registerForm.password, [
    {strategy: 'minLength:6', errorMsg: '密码长度不能少于6位'}
  ]);
  validator. add( registerForm.phoneNumber, [{
    strategy: 'isMobile', errorMsg: '手机号码格式不正确'}
  ]);

  return validator.start()
};

let registerForm = {
  userName: '',
  password: '',
  phoneNumber: ''
};
let errorMsg = validatorFunc();
// 如果 errorMsg 有 确切 的 返回 值， 说明 未 通过 校验
if ( errorMsg ) {
  console.log(errorMsg)
}


/**
 * 优点：
 * 策略模式利用组合、委托和多态等技术和思想，可以有效地避免多重条件选择语句。
 * 策略模式提供了对开放—封闭原则的完美支持，将算法封装在独立的strategy中，使得它们易于切换，易于理解，易于扩展。
 * 策略模式中的算法也可以复用在系统的其他地方，从而避免许多重复的复制粘贴工作。
 * 在策略模式中利用组合和委托来让Context拥有执行算法的能力，这也是继承的一种更轻便的替代方案。
 * 缺点：
 * 首先，使用策略模式会在程序中增加许多策略类或者策略对象，但实际上这比把它们负责的逻辑堆砌在Context中要好。
 * 其次，要使用策略模式，必须了解所有的strategy，必须了解各个strategy之间的不同点，这样才能选择一个合适的strategy。
 * 比如，我们要选择一种合适的旅游出行路线，必须先了解选择飞机、火车、自行车等方案的细节。
 * 此时strategy要向客户暴露它的所有实现，这是违反最少知识原则的。
 **/

S = function(salary) {
  return 4 * salary
};

A = function(salary) {
  return 3 * salary
};

B = function(salary) {
  return 2 * salary
};

// 在函数中隐形使用
calculateBonus = function (func, salary) {
  return func(salary)
};

console.log(calculateBonus(S, 1000));
